#include <omkernel/kernel.h>
#include <omkernel/type.h>
#include <omkernel/stat.h>
#include <omkernel/fs/fs.h>
#include <omkernel/mm/mm.h>
#include <omkernel/driver.h>
#include <omkernel/io.h>
#include <omkernel/dirent.h>
#include <omkernel/rtc.h>
#include <omkernel/errno.h>
#include <lib/string.h>

#include "fat.h"

fatNode_t *dumyvar;
fatNode_t *fatNodeTable;

int fatAddNode(fatNode_t *node)
{
	if(fatNodeTable == NULL)
	{
		fatNodeTable = node;
		fatNodeTable->next = NULL;
		return 1;
	}
	node->next = fatNodeTable;
	fatNodeTable = node;
	return 1;
}

fatNode_t *fatCreateNode(int cluster, int size, fatEntry_t *entry, fatNode_t *parent)
{
	fatNode_t *node = (fatNode_t *)kmalloc(sizeof(fatNode_t));
	if(node == NULL)
		return NULL;
	memset(node, 0, sizeof(fatNode_t));
	node->id = cluster;
	node->cluster = cluster;
	node->size = size;
	node->entry = entry;
	node->parent = parent;
	fatAddNode(node);
#ifdef CONFIG_FAT_DEBUG
	kprintf("%s: node 0x%08x, cluster %d\n", __FUNCTION__, node, cluster);
#endif
	return node;
}


static int fatGetNextCluster(fatfs_t *fs, int cluster)
{
	int nextCluster;
	
	if(cluster >= fs->fatEntries) {
		dprintf("%s(): Warning - attepting to grab cluster outside of "
			"FAT %d, max %d\n",
			__FUNCTION__,
			cluster,
			fs->fatEntries);
		return -1;
	}

	switch(fs->type)
	{
		case FAT_12:
			nextCluster = *(short *)((char *)&fs->fat[((cluster*3)/2)]);
			// if cluster is odd
			if(cluster & 1)
				nextCluster >>= 4;
			else
				nextCluster = FAT_CLUSTER12(nextCluster);
	
			if(nextCluster == FAT_12_ENDOFCLUSTER)
				return -1;
			break;
		
		case FAT_16:
			nextCluster = ((short *)fs->fat)[cluster];
			nextCluster = FAT_CLUSTER16(nextCluster);
			if(nextCluster == FAT_16_ENDOFCLUSTER)
				return -1;
			break;
		case FAT_32:
			nextCluster = ((int *)fs->fat)[cluster];
			nextCluster = FAT_CLUSTER32(nextCluster);
			if(nextCluster == FAT_32_ENDOFCLUSTER)
				return -1;
			break;
		default: return -1;
	}
	return nextCluster;
}


static void fatSetCluster(fatfs_t *fs, uint32_t cluster, uint32_t value)
{
	int nextCluster;
	
	switch(fs->type)
	{
		case FAT_12:
			nextCluster = *(short *)((char *)&fs->fat[((cluster*3)/2)]);
			// if cluster is odd
			if(cluster & 1) {
				*(short *)((char *)&fs->fat[((cluster*3)/2)]) &= 0x000f;
				*(short *)((char *)&fs->fat[((cluster*3)/2)]) |= (value << 4);
			} else {
				*(short *)((char *)&fs->fat[((cluster*3)/2)]) &= 0xf000;
				*(short *)((char *)&fs->fat[((cluster*3)/2)]) |= value;
			}
			break;
		
		case FAT_16:
			((short *)fs->fat)[cluster] = (short)value;
			break;
		case FAT_32:
			((int *)fs->fat)[cluster] = value;
			break;
		default: return;
	}
	return;
}


/*
 * fatGetFreeCluster()
 * Used to find an unused cluster
 * Returns:
 *  Cluster Number if successfull.
 *  -1 for failure.
 */
static int fatGetFreeCluster(fatfs_t *fs) {
	int i = 0;
	int32_t cluster;
	while((cluster = fatGetNextCluster(fs, i)) != 0 && 
	  i < fs->fatEntries) {
		i++;
	}
	if(cluster < 0) {
		dprintf("%s(): error getting cluster\n", __FUNCTION__);
		return -1;
	}

	if(i >= fs->fatEntries) {
		dprintf("%s(): unable to find a free cluster\n", __FUNCTION__);
		return -1;
	}

	return i;
}


uint8_t fatDetermineType(fatBootSector_t *bs)
{
	int rootDirSectors;
	int totalSectors;
	int fatsize;
	int dataSectors;
	int clusterCount;

	rootDirSectors = (((bs->rootEntrys*32) + (bs->bytesPerSec-1))/
						bs->bytesPerSec);
	if(bs->sectorsPerFat16 != 0)
		fatsize = bs->sectorsPerFat16;
	else
		fatsize = bs->type.fat32.fatSize;

	if(bs->totalSectors16 != 0)
		totalSectors = bs->totalSectors16;
	else
		totalSectors = bs->totalSectors32;

	dataSectors = totalSectors - (bs->reservedSecCount+(bs->numFats*fatsize)+
				rootDirSectors);
	
	clusterCount = dataSectors / bs->secPerCluster;

#ifdef CONFIG_FAT_DEBUG
	kprintf("%s: %s\n", __FUNCTION__, 
		(custerCount<4085) ? "FAT12" : (clusterCount<65525) ? "FAT16":"FAT32");
#endif
	if(clusterCount < 4085)
		return FAT_12;
	else if(clusterCount < 65525)
		return FAT_16;
	else
		return FAT_32;
}

fatNode_t *fatGetEntryFromID(int id)
{
	fatNode_t *node = fatNodeTable;
	while(node != NULL)
	{
		if(node->id == id)
			return node;
		node = node->next;
	}
	return NULL;
}

int cluster2block(fatfs_t *fs, int cluster)
{
	return ((cluster - 2) * (fs->clusterSize/fs->blocksize)) + fs->dataStart;
}

int fatrwCluster(fatfs_t *fs, int device, int cluster, char *buffer, int mode)
{
	int block;
	
	block = cluster2block(fs, cluster);

#ifdef CONFIG_FAT_DEBUG
	kprintf("%s: cluster %d -> block %d\n", __FUNCTION__, cluster, block);
#endif
	
	return ioRequest(device, block, fs->clusterSize/fs->blocksize, buffer, 
			(mode == FAT_READ ? IO_READ : IO_WRITE));
}

int fatTranslateMode(fatEntry_t *entry)
{
	int mode = 0;
	
	//dprintf("FatMode: %s d:%d v:%d\n", entry->name, entry->attribute.bits.directory, entry->attribute.bits.volumelabel);	
	if(!entry->attribute.bits.volumelabel && !entry->attribute.bits.directory) 
		mode = (mode | S_IFREG);

	if((entry->attribute.bits.directory && !entry->attribute.bits.volumelabel) || 
		(entry->attribute.bits.volumelabel == -1 && !entry->attribute.bits.directory))
	{
		if(mode)
		{
#ifdef CONFIG_FAT_DEBUG
			kprintf("%s: invalid attribute\n", __FUNCTION__);
#endif
			return -1;
		}
		mode = (mode | S_IFDIR);
	}

	mode = (mode | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
	return mode;
}

char *fatParseName(const char *src)
{
	char name[MAX_FILENAME];
	char *dst;
	int i, j, k;

	j = 0;
	for(i=0; i<8; i++)
	{
		if(src[i] != ' ')
		{
			name[j] = tolower(src[i]);
			j++;
		}
	}
	k = j;
	for(i=8; i<11; i++)
	{
		if(src[i] != ' ') 
		{
			j++;
			name[j] = tolower(src[i]);
		}
	}
	if(j > k)
	{
		name[k] = '.';
		name[j+1] = 0;
	}
	else
		name[k] = 0;

	dst = kstrdup(name);
	return dst;
}

/*
 * fatUnparseName
 *
 * turns a regulare filename into a fat filename "foo.c" -> "FOO     C  "
 * 
 * Args:
 * char *dst: dest buffer, must be at least 12 bytes long
 * const char *src: src string to convert
 * int len: length of src buffer
 */
void fatUnparseName(char *dst, const char *src, int len)
{
	int index=0, i=0;

	/* hard code checks for . and .. */
	if(!strcmp((char *)src, ".")) {
		strncpy(dst, ".          ", 11);
		dst[11] = '\0';
		return;
	}
	if(!strcmp((char *)src, "..")) {
		strncpy(dst, "..         ", 11);
		dst[11] = '\0';
		return;
	}
		
	/* name string dest must at lest be  at least 12 bytes */
	while(src[index] != '.' && len && index < 11) {
		dst[index] = toupper(src[index]);
		index++;
		len--;
	}

	if(!len) {
		while(index < 11) {
			dst[index++] = ' ';
		}
	} else {
		for(i = index; i < 8; i++) {
			dst[i] = ' ';
		}
		index++;
		len--;
		while(len && i < 11) {
			dst[i++] = toupper(src[index]);
			index++;
			len--;
		}
		while(i < 11) {
			dst[i++] = ' ';
		}
	}
	dst[11] = 0;
}


int fatCompareName(fatEntry_t *entry, char *name)
{
	// TODO: check for deleted entry
	if(entry->name[0] == 0)
		return FALSE;

	if(entry->name[0] == 0xe5)
		return 0;	
	
	if(!strncmp(entry->name, name, 11))
		return 1;
	
	return 0;
}

int fatGetIndex(fatfs_t *fs, fatEntry_t *dir, char *name)
{
	int i;
	int clusterEntries;
	char dstName[12];

	fatUnparseName((char *)&dstName, name, strlen(name)); 
	
	clusterEntries = fs->clusterSize / sizeof(fatEntry_t);

	if(dir == NULL || name == NULL)
		return 0;
	for(i=0; i< clusterEntries ; i++)
	{
		if(dir[i].name[0] == 0)
			break;
		if(fatCompareName(&dir[i], dstName) == TRUE)
			return i;
	}
	return -1;
}

int fatOpen(file_t *file)
{
	if(file == NULL)
		return 0;
	return 1;
}


int fatSeek(file_t *file, off_t offset, int from)
{
	if(!file)
		return -1;

	switch(from) {
		case SEEK_SET:
			file->pos = offset;
			break;
		case SEEK_CUR:
			file->pos = file->pos + offset;
			break;
		/*case SEEK_END: // unimplemented */
		default:
			return -ENOSYS;
	}
	return file->pos;
}

int fatRead(file_t *file, char *buffer, int len)
{
	vnode_t *vnode;
	fatNode_t *node;
	fatfs_t *fs;
	char *localbuffer, *lbuffPtr;
	int blocks, cluster;
	int copysize;
	int buffStartOffset;
	int bytesleft, bytesread=0;
	int startCluster, totalClusters;

	if(!file || !file->vnode)
		return -1;
	
	vnode = file->vnode;
	if(vnode->mount->fsInfo == NULL)
		return -1;
	fs = (fatfs_t *)vnode->mount->fsInfo;

	if(vnode->privData == NULL)
		return -1;
	node = (fatNode_t *)vnode->privData;

	/* truncate len if file->pos + len is larger than the file */
	if(file->pos + len > node->size)
		len = node->size - file->pos;

	/* if our current position is >= file size, we must return 0 */
	if(file->pos >= node->size) {
		return 0;
	}

	/* if length is 0, we must return 0 */
	if(len == 0) {
		return 0;
	}
	
	/* figure out how many blocks we need to read */
	blocks = (len/fs->blocksize);
	if((bytesleft = (len % fs->blocksize)) > 0)
		blocks++;

	/*
	 * We must set bytesleft to fs->blocksize if len == fs->blocksize
	 * because when bytesleft is set above, it will equal 0, and 
	 * blocks will be equal to 1. and based on our logic below, we'll
	 * skip the main loop memcpy, as well as the bytesleft memcpy.
	 */ 
	if(len == fs->blocksize)
		bytesleft = fs->blocksize;

	/* 
	 * The case to int and unsigned int are here for a work around of a gcc
 	 * optimization.
 	 */
	startCluster = (uint32_t)file->pos / (int32_t)fs->clusterSize;
	totalClusters = node->size / fs->clusterSize;
	if(node->size % fs->clusterSize) 
		totalClusters++;
	
	if(startCluster > totalClusters) {
		return 0;
	}

	/* Get the cluster number we start at */
	cluster = node->cluster;
	while(startCluster--) {
		cluster = fatGetNextCluster(fs, cluster);
	}

	localbuffer = (char *)kmalloc(fs->clusterSize);
	if(localbuffer == NULL)
		return 0;
	
	/* how far to do we copy into each buffer */
	buffStartOffset = (uint32_t)file->pos % (int32_t)fs->clusterSize;
	copysize = fs->clusterSize - buffStartOffset;
	lbuffPtr = localbuffer + buffStartOffset;
	while(blocks--)
	{
		fatrwCluster(fs, vnode->device, cluster, localbuffer, FAT_READ);
		if(blocks != 0)
		{
			memcpy(buffer, lbuffPtr, copysize);
			buffer += copysize;
			file->pos += copysize;
			bytesread += copysize;
			cluster = fatGetNextCluster(fs, cluster);
			if(buffStartOffset) {
				buffStartOffset = 0;
				copysize = fs->clusterSize;
			}
			if(cluster < 0) {
				return bytesread;
				kfree(localbuffer);
			}
		}
	}
	if(bytesleft)
	{
		memcpy(buffer, lbuffPtr, bytesleft);
		bytesread += bytesleft;
		file->pos += bytesleft;
	}
	kfree(localbuffer);
	return bytesread;
}

int fatWrite(file_t *file, char *buffer, int len)
{
	vnode_t *vnode;
	fatNode_t *node;
	fatNode_t *parentNode;
	fatEntry_t *entry;	
	fatEntry_t *dir;
	fatfs_t *fs;
	char *localbuffer, *lbuffPtr;
	int blocks, cluster, newClusters = 0;
	int copysize;
	int buffStartOffset;
	int bytesleft, bytesread=0;
	int startCluster, totalClusters;
	int newFileSize = 0;
	int index;

	if(!file || !file->vnode)
		return -1;
	
	vnode = file->vnode;
	if(vnode->mount->fsInfo == NULL)
		return -1;
	fs = (fatfs_t *)vnode->mount->fsInfo;

	if(vnode->privData == NULL)
		return -1;
	node = (fatNode_t *)vnode->privData;

	/* if length is 0, we must return 0 */
	if(len == 0)
		return 0;

	/* caluclate new filesize */
	if(file->pos + len > node->size)
		newFileSize = file->pos + len;
	
	/* figure out how many blocks we need to write */
	blocks = (len/fs->clusterSize);
	if((bytesleft = (len % fs->clusterSize)) > 0)
		blocks++;

	/* 
	 * this should handle cases were we are writing < 1cluster of data, but
	 * near the end of a cluster
	 */
	if(blocks == 1 && (((uint32_t)file->pos % (int32_t)fs->clusterSize) + 
	bytesleft) > fs->clusterSize) {
		blocks++;
	}
	
	int endCluster = node->size / fs->clusterSize;
	if(node->size % fs->clusterSize)
		endCluster++;

	if(newFileSize) {
		if(newFileSize > (endCluster * fs->blocksize)) {
			newClusters = 
				(newFileSize - (endCluster*fs->clusterSize)) / fs->clusterSize;
			if((newFileSize - (endCluster*fs->clusterSize) ) % fs->clusterSize)
				newClusters++;
		}
	}

	if(newFileSize) {
		if(newClusters) {
			uint32_t cluster = node->cluster;
			uint32_t lastCluster = 0;
			while(1) {
				lastCluster = cluster;
				cluster = fatGetNextCluster(fs, cluster);
				if(fs->type == FAT_12) {
					if(FAT_CLUSTER12(cluster) == FAT_12_ENDOFCLUSTER) {
						break;
					}
					continue;
				} else if(fs->type == FAT_16) {
					if(FAT_CLUSTER16(cluster) == FAT_16_ENDOFCLUSTER) {
						break;
					}
					continue;
				} else if(fs->type == FAT_32) {
					if(FAT_CLUSTER32(cluster) == FAT_32_ENDOFCLUSTER) {
						break;
					}
					continue;
				} else {
					// why the hell is this not a valid fs?
					return -EIO;
				}
			}
			while(newClusters--) {
				int cluster = fatGetFreeCluster(fs);
				fatSetCluster(fs, lastCluster, cluster);
				fatSetCluster(fs, cluster, 0x0fffffff);
				lastCluster = cluster;
			}
		}
	}

	/* 
	 * The case to int and unsigned int are here for a work around of a gcc
 	 * optimization.
 	 */
	/*
	 * FIXME: there will be a problem if lseek is set to > current file size
	 */
	startCluster = (uint32_t)file->pos / (int32_t)fs->clusterSize;
	if(newFileSize) {
		totalClusters = newFileSize / fs->clusterSize;
		if(newFileSize % fs->clusterSize) {
			totalClusters++;
		}
	} else {	
		totalClusters = node->size / fs->clusterSize;
		if(node->size % fs->clusterSize) {
			totalClusters++;
		} else if(node->size == 0) {
			totalClusters++;
		}
	}
	
	if(startCluster > totalClusters)
		return 0;

	/* Get the cluster number we start at */
	cluster = node->cluster;
	while(startCluster--) {
		cluster = fatGetNextCluster(fs, cluster);
		if(cluster < 0) {
			dprintf("fatWrite: start cluster past end of file\n");
			return -EIO;
		}
	}

	localbuffer = (char *)kmalloc(fs->clusterSize);
	if(localbuffer == NULL)
		return 0;
	
	/* how far into buffer do we start to copy data */
	buffStartOffset = (uint32_t)file->pos % (int32_t)fs->clusterSize;
	copysize = fs->clusterSize - buffStartOffset;
	lbuffPtr = localbuffer + buffStartOffset;
	while(blocks--)
	{
		
		if(buffStartOffset) {
			fatrwCluster(fs, vnode->device, cluster, localbuffer, FAT_READ);
		}
		if(blocks != 0)
		{
			memcpy(lbuffPtr, buffer, copysize);
			fatrwCluster(fs, vnode->device, cluster, localbuffer, FAT_WRITE);
			buffer += copysize;
			file->pos += copysize;
			bytesread += copysize;
			cluster = fatGetNextCluster(fs, cluster);
			if(buffStartOffset) {
				lbuffPtr = localbuffer;
				buffStartOffset = 0;
				copysize = fs->clusterSize;
			}
			if(cluster < 0) {
				kfree(localbuffer);
				/* possibly return -EIO? */
				return bytesread;
			}
		}
	}
	if(bytesleft)
	{
		fatrwCluster(fs, vnode->device, cluster, localbuffer, FAT_READ);
		memcpy(lbuffPtr, buffer, bytesleft);
		fatrwCluster(fs, vnode->device, cluster, localbuffer, FAT_WRITE);
		bytesread += bytesleft;
		file->pos += bytesleft;
	}

	memset(localbuffer, 0, fs->clusterSize);

	parentNode = node->parent;

	if(!parentNode) {
		kfree(localbuffer);
		return -EIO;
	}

	/* get the fat entry of the file */
	entry = node->entry;
			
	fatrwCluster(fs, vnode->device, parentNode->cluster, localbuffer, FAT_READ);
	dir = (fatEntry_t *)localbuffer;
	
	/* search for file in parents directory cluster */	
	int nextCluster = 0;
	do {
		/*
		 * The first time we run through this, we set nextCluster
		 */
		if(nextCluster == 0)
			nextCluster = parentNode->cluster;
		else {
			fatrwCluster(fs, vnode->device, nextCluster, localbuffer, FAT_READ);
			dir = (fatEntry_t *)localbuffer;
		}
		index = fatGetIndex(fs, dir, entry->name);
		if(index >= 0)
			break;
		nextCluster = fatGetNextCluster(fs, nextCluster);
	} while(nextCluster != -1);

	if(index < 0) {
		kfree(localbuffer);
		return -EIO;
	}

	/* write the new file size where it needs to be written */
	if(newFileSize) {
		dir[index].fileSize = newFileSize;
		entry->fileSize = newFileSize;
		node->size = newFileSize;
	}
	
	rtcTime_t rtc;
	rtcGetDateTime(&rtc);
	FAT_SET_TIME(entry->time, rtc.hour, rtc.min, rtc.sec);
	FAT_SET_DATE(entry->date, rtc.day, rtc.mon, rtc.year);
	FAT_SET_DATE(entry->access, rtc.day, rtc.mon, rtc.year);

	fatrwCluster(fs, vnode->device, nextCluster, localbuffer, FAT_WRITE);

	/* Write new FAT and then read it back in */
	ioRequest(vnode->device, fs->reservedSectors, fs->fatSectors,
        fs->fat, IO_WRITE);
	ioRequest(vnode->device, fs->reservedSectors, fs->fatSectors,
        fs->fat, IO_READ);

	
	kfree(localbuffer);
	return bytesread;
}


fileOperations_t fatops = 
{
	fatSeek,
	fatRead,
	fatWrite,
	NULL,
	fatOpen,
	NULL
};

int findFatVnode(vnode_t **dst, vnode_t *src, char *_name)
{
	fatNode_t *node;
	fatNode_t *parentNode;
	fatEntry_t *entry;
	fatEntry_t *dir;
	fatfs_t *fs = (fatfs_t *)src->mount->fsInfo;
	char *buffer, *name;
	int index;
	int nextCluster;
	int dstCluster;

	*dst = NULL;

	/* sanity checks */
	if(src == NULL || _name == NULL)
		return 0;

	name = kstrdup(_name);
	if(name == NULL)
		return 0;

	/* we should only be looking for another file if the src vnode is a dir */
	if(!S_ISDIR(src->mode)) {
		dprintf("%s(): src vnode is not a directory!\n", __FUNCTION__);
		return 0;
	}

	parentNode = (fatNode_t *)src->privData;

	buffer = (char *)kmalloc(fs->clusterSize);
	if(buffer == NULL) {
		kfree(name);
		return 0;
	}
	memset(buffer, 0, fs->clusterSize);

	/* 
	 * Read directory cluster, but check if we are 
 	 * reading from the root directory. If we are
 	 * then we can use its node->entry as the 
 	 * directory cluster. This should probably
 	 * be handled better =/
	 */
	if(parentNode->entry->attribute.bits.volumelabel) {
		dir = parentNode->entry;
	} else {
		if(!fatrwCluster(fs, src->device, parentNode->cluster, 
						buffer, FAT_READ))
		{
			dprintf("Error: reading cluster %d\n", parentNode->cluster);
			kfree(buffer);
			kfree(name);
			return 0;
		}
		dir = (fatEntry_t *)buffer;
	}

	
	// Loop this for each cluster in chain	
	nextCluster = 0;
	do {
		/*
		 * The first time we run through this, we set nextCluster
		 */
		if(nextCluster == 0)
			nextCluster = parentNode->cluster;
		else {
			fatrwCluster(fs, src->device, nextCluster, buffer, FAT_READ);
			dir = (fatEntry_t *)buffer;
		}
		index = fatGetIndex(fs, dir, name);
		if(index >= 0)
			break;
		nextCluster = fatGetNextCluster(fs, nextCluster);
	} while(nextCluster != -1);

	if(index < 0) {
		kfree(buffer);
		kfree(name);
		return 0;	
	}

	/* 
	 * if we dont know what Cluster .. points to, use the parrent of its
	 * parent dir cluster. This happens specifically by design one level
	 * below the root dir unfortunately.
	 */	
	if(strcmp(name, "..") == 0) {
		dstCluster = parentNode->parent->cluster;
	} else {
		dstCluster = (dir[index].clusterHigh << 16) | dir[index].cluster;
	}

	node = fatGetEntryFromID(dstCluster);
	if(node == NULL)
	{	
		entry = (fatEntry_t *)kmalloc(sizeof(fatEntry_t));
		memcpy(entry, &dir[index], sizeof(fatEntry_t));				
		node = fatCreateNode(dstCluster, dir[index].fileSize, entry,parentNode);
		if(node == NULL)
		{
			kfree(entry);
			kfree(name);
			kfree(buffer);
			return 0;
		}
	}
	*dst = vfsGetVnode(src->mount, node->id);
	if(*dst == NULL)
	{
		kfree(name);
		kfree(node);
		kfree(entry);
		kfree(buffer);
		return 0;
	}
	(*dst)->device = src->device;
	(*dst)->mode = fatTranslateMode(node->entry);
	if((*dst)->fops == NULL)
		(*dst)->fops = &fatops;
	
	kfree(buffer);
	kfree(name);

	return 1;
}


int fatMount(mount_t *mount, int device, vnode_t **rootv)
{
	fatfs_t *fatfs;
	fatBootSector_t *fatboot;
	fatEntry_t *rootentry = NULL;
	int rootDirOffset = 0;
	char buffer[512];
	
	fatfs = (fatfs_t *)kmalloc(sizeof(fatfs_t));
	if(fatfs == NULL)
		return 0;
	memset(fatfs, 0, sizeof(fatfs_t));


	if(ioRequest(device, 0, 1, (char *)&buffer, IO_READ) == 0) {
		kfree(fatfs);
		return 0;
	}
	fatboot = (fatBootSector_t *)buffer;

	// There has to be another way to determine if this is a fatfs
	if(fatboot->bytesPerSec != 512)
	{
		kprintf("%s: not a FAT filesystem\n", __FUNCTION__);
		kfree(fatfs);
		return 0;
	}
	fatfs->type = fatDetermineType(fatboot);
	fatfs->rootEntries = fatboot->rootEntrys;
	fatfs->fatCount = fatboot->numFats;

	/*
	 * TODO: Add checks here for all these values. clusterSize is used a lot
	 * for calculating how much memory to write, and this could be controlled
	 * by the user when mounting.
	 */
	fatfs->clusterSize = fatboot->secPerCluster * fatboot->bytesPerSec;
	fatfs->blocksize = fatboot->bytesPerSec;
	if(fatboot->sectorsPerFat16 == 0)
	{
		fatfs->fatSize = fatboot->type.fat32.fatSize * fatboot->bytesPerSec;
		fatfs->fatSectors = fatboot->type.fat32.fatSize;
	}
	else
	{
		fatfs->fatSize = fatboot->sectorsPerFat16 * fatboot->bytesPerSec;
		fatfs->fatSectors = fatboot->sectorsPerFat16;
	}
	fatfs->reservedSectors = fatboot->reservedSecCount;
	fatfs->hiddenBlocks = fatboot->hidden;
	fatfs->rootEntryPos = ((fatfs->rootEntries * 32) + (fatfs->blocksize-1))/fatfs->blocksize;
	fatfs->dataStart = fatfs->reservedSectors + (fatfs->fatCount * (fatfs->fatSize / fatfs->blocksize)) + fatfs->rootEntryPos;

	if(fatboot->totalSectors16 != 0) {
		/* fat 12/16 */
		fatfs->fatEntries = ((fatboot->totalSectors16 - fatfs->dataStart) / 
			fatboot->secPerCluster);
	} else {
		/* fat 32 */
		fatfs->fatEntries = ((fatboot->totalSectors32 - fatfs->dataStart) / 
			fatboot->secPerCluster);
	}
	fatfs->fat = (char *)kmalloc(fatfs->fatSize);
	if(fatfs->fat == NULL)
	{
		kfree(fatfs);
		return 0;
	}	
	if(!ioRequest(device, fatfs->reservedSectors, fatfs->fatSectors, 
		fatfs->fat, IO_READ))
	{
		kprintf("%s: failed to mount volume, ioRequest failed\n", __FUNCTION__);
		kfree(fatfs);
		return 0;
	}
	
	if(fatfs->type == FAT_32)
	{
		char *buffer = (char *)kmalloc(fatfs->clusterSize);
		rootDirOffset = cluster2block(fatfs, fatboot->type.fat32.rootCluster);
		if(!ioRequest(device, rootDirOffset, fatfs->clusterSize/fatfs->blocksize, buffer, IO_READ))
		{
			kfree(fatfs->fat);
			kfree(fatfs);
			return 0;
		}
		rootentry = (fatEntry_t *)buffer;
	}
	else /* fat12 & fat16 */
	{
		rootentry = (fatEntry_t *)kmalloc(sizeof(fatEntry_t)*fatboot->rootEntrys);
		if(rootentry == NULL)
		{	
			kfree(fatfs->fat); 	
			kfree(fatfs);
			return 0;
		}
		memset(rootentry, 0, sizeof(fatEntry_t));
		rootDirOffset = (fatfs->fatCount * fatfs->fatSectors) + fatfs->reservedSectors;
	
		if(!ioRequest(device, rootDirOffset, (sizeof(fatEntry_t)*fatboot->rootEntrys)/fatfs->blocksize, (char *)rootentry, IO_READ))
		{
			kfree(fatfs->fat);
			kfree(fatfs);
			return 0;
		}
	}
	fatNode_t *fatnode = fatCreateNode(rootDirOffset, 0, rootentry, 0);

	*rootv = createVnode();
	if(*rootv == NULL)
		return 0;
	
	(*rootv)->id = fatnode->id;
	(*rootv)->device = device;
	(*rootv)->mode = fatTranslateMode(rootentry) | S_IFDIR;
	(*rootv)->fops = &fatops;
	(*rootv)->privData = (char *)fatnode;
	mount->fsInfo = (void *)fatfs;
	kprintf("fat: mounted fat%d filesystem\n", fatfs->type);
	return 1;
}


int fatCreateEntry(vnode_t *parentVnode, char *name, int dir) 
{
	fatNode_t *node;
	fatEntry_t *entry;
	fatEntry_t *newDir;
	fatfs_t *fs;
	int i;
	int error = 0;
	uint32_t nextCluster;
	uint32_t newCluster;
	uint32_t curCluster;
	char *buffer;
	char fatName[12];


	dprintf("FatCreateEntry %s\n", name);

	if(!parentVnode || !name)
		return -1;
	
	if(!parentVnode->privData)
		return -1;

	if(!parentVnode->mount || !parentVnode->mount->fsInfo)
		return -1;

	node = (fatNode_t *)parentVnode->privData;

	if(!node->entry)
		return -1;

	fs = (fatfs_t *)parentVnode->mount->fsInfo;

	buffer = (char *)kmalloc(fs->clusterSize);
	if(buffer == NULL)
		return -1;

	memset(buffer, 0, fs->clusterSize);

	fatrwCluster(fs, parentVnode->device, node->cluster, buffer, FAT_READ);
	entry = (fatEntry_t *)buffer;

	// Loop this for each cluster in chain	
	nextCluster = 0;
	do {
		/*
		 * The first time we run through this, we set nextCluster
		 */
		if(nextCluster == 0)
			curCluster = nextCluster = node->cluster;
		else {
			curCluster = nextCluster;
			fatrwCluster(fs, parentVnode->device, nextCluster, buffer,FAT_READ);	
			entry = (fatEntry_t *)buffer;
		}
		
		/* see if the file already exists */
		if(fatGetIndex(fs, entry, name) != -1) {
			kfree(buffer);	
			return -EEXIST;
		}
		
		/* find an entry that has previously been deleted, or is free */
		for(i=0; !error && (i < fs->clusterSize / sizeof(fatEntry_t)); i++) {
			if(entry[i].name[0] == 0 || entry[i].name[0] == 0xe5)
				break;
		}
		if(i < (fs->clusterSize / sizeof(fatEntry_t)))
			break;
	
		nextCluster = fatGetNextCluster(fs, nextCluster);
		if(nextCluster < 0)
			break;
	} while(nextCluster);

	//dprintf("FatMakeDirectory: using entry %d\n", i);

	/* did we get a legit entry? */	
	if(i > fs->clusterSize/sizeof(fatEntry_t) || nextCluster < 0) {
		kfree(buffer);
		return -1;
	}
	
	memset(&entry[i], 0, sizeof(fatEntry_t));
	memset(entry[i].name, 0x20, 11);
	
	fatUnparseName((char *)&fatName, name, strlen(name));
	
	strncpy(entry[i].name, fatName, 11);
	
	if(dir)
		entry[i].attribute.bits.directory=1;

	rtcTime_t rtc;
	rtcGetDateTime(&rtc);

	dprintf("FatTime: %d-%d-%d %d:%d:%d\n", 
		rtc.mon, rtc.day, rtc.year + 2000, rtc.hour, rtc.min, rtc.sec);

	FAT_SET_TIME(entry[i].ctime, rtc.hour, rtc.min, rtc.sec);
	FAT_SET_DATE(entry[i].cdate, rtc.day, rtc.mon, rtc.year);

	FAT_SET_DATE(entry[i].access, rtc.day, rtc.mon, rtc.year);

	FAT_SET_TIME(entry[i].time, rtc.hour, rtc.min, rtc.sec);
	FAT_SET_DATE(entry[i].date, rtc.day, rtc.mon, rtc.year);
	
	/* get a free cluster */
	newCluster = fatGetFreeCluster(fs);
	if(newCluster < 0) {
		kfree(buffer);
		return -1;
	}
	entry[i].cluster = newCluster & 0xffff;
	entry[i].clusterHigh = (newCluster >> 16) & 0xffff;

	/* we need to create a fresh directory cluster for the new directory */
	if(dir) {
		newDir = (fatEntry_t *)kmalloc(fs->clusterSize);
		if(!newDir) {
			kfree(buffer);
			return -1;
		}
		
		memset(newDir, 0, fs->clusterSize);
	
		/* the new directory cluster needs to have an entry to itself */
		memset(newDir[0].name, 0x20, 11);
		strncpy(newDir[0].name, ".", 1);
		newDir[0].attribute.bits.directory=1;
		newDir[0].cluster = newCluster & 0xffff;
		newDir[0].clusterHigh = (newCluster >> 16) & 0xffff;

		memset(newDir[1].name, 0x20, 11);
		strncpy(newDir[1].name, "..", 2);
		newDir[0].attribute.bits.directory=1;
		newDir[0].cluster = node->cluster & 0xffff;
		newDir[0].clusterHigh = (node->cluster >> 16) & 0xffff;
	}
	
	/* mark the new cluster end */
	fatSetCluster(fs, newCluster, 0x0fffffff);
	//dprintf("FatMakeDirectory: writing entry %d cluster %d, new cluster %d\n", 
	//	i, curCluster, newCluster);

	/* write the new directory entry */
	fatrwCluster(fs, parentVnode->device, curCluster, 
		(char *)entry, FAT_WRITE);

	/* write the new directory's cluster */
	if(dir) {
		fatrwCluster(fs, parentVnode->device, newCluster, 
			(char *)newDir, FAT_WRITE);
	}
	

	/* Write new FAT and then read it back in */
	ioRequest(parentVnode->device, fs->reservedSectors, fs->fatSectors,
        fs->fat, IO_WRITE);
	ioRequest(parentVnode->device, fs->reservedSectors, fs->fatSectors,
        fs->fat, IO_READ);

	kfree(buffer);
	kfree(newDir);
	return 0;	

}


int fatMakeDirectory(vnode_t *parentVnode, char *name)
{
	return fatCreateEntry(parentVnode, name, 1);
}

int fatReadDirectory(file_t *file, void *dirent, filldir_t filldir)
{
	fatNode_t *node;
	fatEntry_t *entry;
	int i, j, cluster;
	int error = 0;
	fatfs_t *fs;
	char *dirname;
	char *buffer;
	int nextcluster = 0;

	/* sanity checks */	
	if(file == NULL)
		return 0;

	if(!file->vnode || !file->vnode->privData)
		return 0;

	if(!S_ISDIR(file->vnode->mode)) 
		return 0;

	node = (fatNode_t *)file->vnode->privData;
	if(node->entry == NULL)
		return 0;

	if(file->vnode->mount->fsInfo == NULL)
		return 0;


	/*
	 * If this is the Root directory, use the node->entry,
	 *  otherwise allocate a buffer to read in directory entries
	 */
	fs = (fatfs_t *)file->vnode->mount->fsInfo;
	
	buffer = (char *)kmalloc(fs->clusterSize);
	if(buffer == NULL)
		return 0;
	memset(buffer, 0, fs->clusterSize);

	if(node->entry->attribute.bits.volumelabel) {
		entry = node->entry;
	} else {

	fatrwCluster(fs, file->vnode->device, node->cluster, buffer, FAT_READ);
	entry = (fatEntry_t *)buffer;
	}

	i = file->pos / sizeof(fatEntry_t);
	j = 0;	
	do
	{
		//dprintf("NextCluster: %d\n", nextcluster);	

		if(nextcluster) {
			fatrwCluster(fs, file->vnode->device, nextcluster, 
				buffer, FAT_READ);
			entry = (fatEntry_t *)buffer;
		} else {
			nextcluster = node->cluster;
		}

		for(i=0; !error && (i < (fs->clusterSize / sizeof(fatEntry_t))); i++, j++)
		{
			// see if its a valid entry
			if(entry[i].name[0] == 0xe5)
				continue;

			// Shouldn't be any more entries after this
			if(entry[i].name[0] == 0) {
				kfree(buffer);
				return 1;
			}
			
			if(entry[i].fileSize == 0xffffffff)
				continue; 
			//kprintf("%s ", fatParseName(entry[i].name));	
			/* fake . and .. because root directory doesn't have them */
			/*if(j == 0 || j == 1)
			{	
				if(entry[i].name[0] != '.')
				{
					dirname = kstrdup(j==0?".":"..");
					// FIXME: figure out what cluster should be faked here!!!
					cluster = cluster2block(fs, 1);
					error = filldir(dirent, dirname, strlen(dirname),
									(j*sizeof(fatEntry_t)), cluster);
					continue; 
				}
			}*/
			
			/* dirname is allocated inside fatParseName, so remember free it */
			dirname = fatParseName(entry[i].name);

			//if(j == 1 && !strcmp("..      ", entry[i].name))
			//	cluster = cluster2block(fs, 1);
			//else
				cluster = entry[i].cluster | (entry[i].clusterHigh << 16);

				/* 
				 * this is a work around for readdir() in newlibc.
				 * if dp->d_ino == 0 it will skip over, so we will set it
				 * to -1, except for the volumelabel.
				 */
				if(cluster == 0 && (entry[i].attribute.bits.volumelabel == 0))
					cluster = -1;
			//dprintf("entry: %s, %d, %d\n", dirname, cluster, entry[i].fileSize);
			error = filldir(dirent, dirname, strlen(dirname), 
				(j*sizeof(fatEntry_t)), cluster);
			
			/* 
			 * Dont forget this needed to be free'd from the 
			 * call to fatParseName earlier
			 */
			kfree(dirname);
			
			/* update file position */
			file->pos += sizeof(fatEntry_t);
			
			//dprintf("Fat: %s, %08x, %d\n", entry[i].name, entry[i].fileSize, entry[i].cluster | (entry[i].clusterHigh << 16));
		}
	} while((nextcluster = fatGetNextCluster(fs, nextcluster)) > 0);

	kfree(buffer);
	return 1;
}

int fatCreate(vnode_t *parent, char *name, mode_t mode)
{
	return fatCreateEntry(parent, name, 0);	
}

int fatRename(vnode_t *parent, char *name)
{
	fatNode_t *node;
	fatEntry_t *entry;
	fatfs_t *fs;
	char *buffer;

	/* sanity checks */
	if(!parent || !name)
		return -1;

	if(!parent->privData)
		return -1;

	if(!parent->mount || !parent->mount->fsInfo)
		return -1;

	/* Get fatNode_t of parent directory and fatfs_t */
	node = (fatNode_t *)parent->privData;
	fs = (fatfs_t *)parent->mount->fsInfo;

	/* get parenty directory cluster */
	buffer = (char *)kmalloc(fs->clusterSize);
	memset(buffer, 0, fs->clusterSize);

	fatrwCluster(fs, parent->device, node->parent->cluster, buffer, FAT_READ);

	entry = (fatEntry_t *)buffer;

	/* find the file we want to rename */	
	int index = fatGetIndex(fs, entry, node->entry->name);
	if(index < 0) {
		kfree(buffer);
		return -1;
	}

	/* upper case new name */
	char fatName[12];
	fatUnparseName((char *)&fatName, name, strlen(name));
	
	/* clear entry name and rewrite the name */
	memset(entry[index].name, 0x20, 11);
	strncpy(entry[index].name, fatName, 11);
	
	memset(node->entry->name, 0x20, 11);
	strncpy(node->entry->name, fatName, 11);

	/* write the entry back to the disk */
	fatrwCluster(fs, parent->device, node->parent->cluster, (char *)entry, FAT_WRITE);

	kfree(buffer);		
	return -1;
}


/*
 * fatRemove
 *
 * Removes a file on in the fat filesystem
 *
 * Args:
 * vnode_t *vnode: vnode of file to remove
 *
 * Returns:
 * 0 upon success. negative errno on error.
 */
int fatRemove(vnode_t *vnode)
{
	fatfs_t *fs;
	fatNode_t *node;
	fatNode_t *parent;
	fatEntry_t *entry;
	fatEntry_t *dir;
	char *buffer;
	int index;
	int lastCluster = 0;
	int nextCluster = 0;

	if(!vnode)
		return -EIO;

	if(!vnode->privData)
		return -EIO;

	if(!vnode->mount || !vnode->mount->fsInfo)
		return -EIO;
	
	node = (fatNode_t *)vnode->privData;
	entry = node->entry;

	fs = vnode->mount->fsInfo;
	
	parent = node->parent;
	if(!parent)
		return -EIO;

	buffer = (char *)kmalloc(fs->clusterSize);
	if(!buffer)
		return -ENOMEM;

	
	if(fatrwCluster(fs, vnode->device, parent->cluster, buffer, FAT_READ) == 0) {
		kfree(buffer);
		return -EIO;
	}

	dir = (fatEntry_t *)buffer;

	/*
	 * TODO: Move this into a function
	 */
	do {
		/*
		 * The first time we run through this, we set nextCluster
		 */
		if(nextCluster == 0)
			nextCluster = parent->cluster;
		else {
			fatrwCluster(fs, vnode->device, nextCluster, buffer, FAT_READ);
			dir = (fatEntry_t *)buffer;
		}
		index = fatGetIndex(fs, dir, entry->name);
		if(index >= 0)
			break;
		nextCluster = fatGetNextCluster(fs, nextCluster);
	} while(nextCluster != -1);

	if(index < 0 || nextCluster < 0) {
		kfree(buffer);
		return -ENOENT;	
	}

	dir[index].name[0] = 0xe5; // FAT_DELETE

	if(!fatrwCluster(fs, vnode->device, nextCluster, buffer, FAT_WRITE)) {
		kfree(buffer);
		return -EIO;
	}

	nextCluster = 0;	
	do {
		/*
		 * The first time we run through this, we set nextCluster
		 */
		if(nextCluster == 0) {
			lastCluster = nextCluster = node->cluster;
			nextCluster = fatGetNextCluster(fs, nextCluster);
			fatSetCluster(fs, lastCluster, 0xfffffff8);
		} else {

			lastCluster = nextCluster;
		
			nextCluster = fatGetNextCluster(fs, nextCluster);
			fatSetCluster(fs, lastCluster, 0);
		}
	} while(nextCluster != -1);

	ioRequest(vnode->device, fs->reservedSectors, fs->fatSectors,
        fs->fat, IO_WRITE);
	ioRequest(vnode->device, fs->reservedSectors, fs->fatSectors,
        fs->fat, IO_READ);

	kfree(buffer);	
	return 0;	
}

int fatStat(file_t *file, struct stat *buf)
{
	vnode_t *vnode;
	fatNode_t *node;
	fatfs_t *fs;

	if(file == NULL || buf == NULL) {
		dprintf("fatStat: null file or buf\n");
		return -EINVAL;
	}

	if(file->vnode == NULL) {
		dprintf("fatStat: null vnode\n");
		return -EIO;
	}

	vnode = file->vnode;
	if(vnode->privData == NULL) {
		dprintf("fatStat: null fatNode\n");
		return -EIO;
	}
	node = (fatNode_t *)vnode->privData;

	fs = (fatfs_t *)vnode->mount->fsInfo;
	
	memset(buf, 0, sizeof(struct stat));
	
	buf->st_dev = (dev_t)vnode->device; 
	buf->st_ino = (ino_t)node->cluster;
	buf->st_mode = fatTranslateMode(node->entry); 

	if(!node->entry->attribute.bits.volumelabel && 
	   !node->entry->attribute.bits.directory) {
			buf->st_size = (off_t)(node->size);
	} else {
			buf->st_size = fs->clusterSize;
	}
		
	buf->st_blksize = fs->blocksize;
	return 0;
}

int fatGetVnodeData(int vnid, char **data)
{
	*data = (char *)fatGetEntryFromID(vnid);
	return 1;
}



fscalls_t fatfscalls= 
{
	findFatVnode,
	fatMount,
	fatGetVnodeData,
	fatMakeDirectory,
	fatReadDirectory,
	fatCreate,
	fatRename,
	fatRemove,
	fatStat,
};

void initFat(void)
{
	// register fs here
	registerFileSystem("fat", &fatfscalls);
	fatNodeTable = NULL;
}	
